Skip to content

Guard against a null asset in `MuxMirrorFieldtype::preload() - #99

Merged
daun merged 1 commit into
daun:mainfrom
jacksleight:fix/null-asset-in-mux-mirror-preload
Jun 30, 2026
Merged

Guard against a null asset in `MuxMirrorFieldtype::preload()#99
daun merged 1 commit into
daun:mainfrom
jacksleight:fix/null-asset-in-mux-mirror-preload

Conversation

@jacksleight

Copy link
Copy Markdown
Contributor

TLDR

This PR adds a null check in MuxMirrorFieldtype::preload() because depending on your data Statamic's $this->asset() can return null in certain circumstances, resulting in a Call to a member function isVideo() on null error. The fix is tiny and perfectly safe as far as I can tell.

Below is Claude's full explanation of the issue and how to reproduce it, if you really wanted to dig into it 🙂 .


Summary

MuxMirrorFieldtype::preload() calls $asset->isVideo() on a value that can be null, which throws a fatal error and returns a 500 for any control panel screen that builds the meta for an assets field whose container blueprint contains a mux_mirror field:

Call to a member function isVideo() on null
  at src/Fieldtypes/MuxMirrorFieldtype.php:60

In practice this takes down entry edit screens (and anywhere else an assets field is preloaded), not just the asset editor.

The fix

preload() already treats $asset as nullable on the line directly above ('is_asset' => (bool) $asset), but the next line dereferences it unconditionally:

return [
    'is_asset' => (bool) $asset,
    'is_video' => $asset->isVideo(),        // 💥 $asset can be null here
    ...
];

The one-line change mirrors the null handling already used for is_proxy:

-            'is_video' => $asset->isVideo(),
+            'is_video' => $asset?->isVideo() ?? false,

Root cause

MuxMirrorFieldtype::asset() returns null whenever the field's parent is not an Asset instance:

protected function asset(): ?Asset
{
    if ($this->field?->parent() instanceof Asset) {
        return $this->field->parent();
    }

    return null;
}

That happens during normal control panel usage because of how Statamic builds an asset's blueprint meta:

  1. An assets field's preload() (Statamic\Fieldtypes\Assets\Assets) renders an inline preview of each selected asset via Statamic\Http\Resources\CP\Assets\AssetsFieldtypeAsset::publishFormData(), which builds the asset blueprint's field meta — including the mux_mirror field.
  2. AssetContainer::blueprint($asset = null) returns a single, Blink-cached blueprint object and mutates its parent in place: setParent($asset ?? $this).
  3. The same Assets::preload() also calls getColumns(), which calls $this->container()->blueprint() with no asset, setting that shared blueprint's parent to the container.
  4. Because the blueprint object (and its built fields()) are cached and shared, a later asset preview ends up meta-ing the mux_mirror field with the AssetContainer as its parent instead of an Asset. asset() returns null, and $asset->isVideo() throws.

The underlying shared-mutable-blueprint behaviour is in Statamic core (present at least as far back as v5.74 / v6.0.0, so it is not a recent regression), but the addon is the thing that crashes on it. Guarding preload() makes the fieldtype resilient to being meta'd with a non-asset parent, which is the contract the rest of the method already assumes.

Reproduction

A minimal, isolated reproduction needs no Mux account, no API credentials and no mirrored video — just an image asset and the mux_mirror field on the container blueprint.

  1. Fresh Statamic 6 site (reproduced on v6.23.0, PHP 8.5).

  2. Add a mux_mirror field to the asset container blueprint (resources/blueprints/assets/assets.yaml):

    -
      handle: mux
      field:
        type: mux_mirror
        display: Mux
  3. Give a collection two assets fields pointing at that container (image_one, image_two).

  4. Upload one image and select the same asset in both fields on one entry.

  5. Open that entry in the control panel → 500.

Why two fields / the same asset

Within Assets::preload(), getItemData() (the asset preview) runs before getColumns() (the call that poisons the shared blueprint), so a single field cannot crash its own preview. With two fields referencing the same asset:

  • image_one previews the asset (caches its blueprint, parent = asset, fine), then its getColumns() poisons that cached blueprint (parent → container).
  • image_two re-previews the same asset → cache hit on the now-poisoned blueprint → the mux_mirror field is meta'd with the container as parent → isVideo() on null → 500.

Impact

  • Severity: any control panel edit screen that surfaces an assets field tied to a container with a mux_mirror field can 500. It can present as "every entry is broken" once content references such assets.
  • Risk of the change: minimal — one expression, no behavioural change when $asset is a real asset; is_video simply becomes false when there is no asset, consistent with is_asset being false in the same payload.

@daun

daun commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Nice, thanks! I don't think I'll be reading all that :) The fix looks plausible enough.

I'd like to add a regression test for this, but let's get this fix out and I'll see about the test later.

@daun daun added the bug Something isn't working label Jun 30, 2026
@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.40%. Comparing base (dc02209) to head (5a9e8b7).
⚠️ Report is 18 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##               main      #99   +/-   ##
=========================================
  Coverage     62.40%   62.40%           
  Complexity      829      829           
=========================================
  Files            68       68           
  Lines          2825     2825           
=========================================
  Hits           1763     1763           
  Misses         1062     1062           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@daun
daun merged commit f1aaed8 into daun:main Jun 30, 2026
8 checks passed
@daun

daun commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Should be published as 3.4.1. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants